﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Xml.Linq;
using System.Windows.Input;
using System.Text.RegularExpressions;
using System.Collections.ObjectModel;

namespace NetLogger
{
	public class NetMember : NetPropertyBase, INetPost, INetMapMarker
	{
		public const string MemberBaseIconFile = "transmit_blue.png";
		public const string MemberBaseReportIconFile = "transmit_blue_rpt.png";
		public const string MemberMobileIconFile = "car.png";
		public const string MemberMobileReportIconFile = "car_rpt.png";
		public const string ReportIconFile = "weather_lightning.png";

		private bool _isPostUpdate;
		private bool _inPropertyChange;
		private bool _inConstruction;
		private string _previousCallsign;
		private NetLocation _baseLocation;
		private Guid _updateId = Guid.Empty;

		private bool _isNewAddition = true;
		public bool IsNewAddition
		{
			get
			{
				return _isNewAddition;
			}
			set
			{
				_isNewAddition = value;
				OnPropertyChanged( "IsNewAddition" );
			}
		}

		private bool _isPosted;
		public bool IsPosted
		{
			get
			{
				return _isPosted;
			}
		}

		private string _callsign;
		public string Callsign
		{
			get
			{
				return _callsign;
			}
			set
			{
				if ( _callsign != value )
				{
					if ( _isPosted && !_isPostUpdate )
						_previousCallsign = _callsign;
					_callsign = value;
					_toString = null;
					OnPropertyChanged( "Callsign" );
					OnPropertyChanged( "NearestStationLabel" );
					DoNotifications( NetActivityTypes.NoActivity );
					foreach ( NetActivity item in NetActivation.Instance.Activities.Where( a => a.Member == this ) )
						item.OnMemberCallsignNameUpdated();
					UpdateAprsMatches();
				}
			}
		}

		private string _name;
		public string Name
		{
			get
			{
				return _name;
			}
			set
			{
				if ( _name != value )
				{
					_name = value;
					_toString = null;
					OnPropertyChanged( "Name" );
					OnPropertyChanged( "NearestStationLabel" );
					DoNotifications( NetActivityTypes.NoActivity );
					foreach ( NetActivity item in NetActivation.Instance.Activities.Where( a => a.Member == this ) )
						item.OnMemberCallsignNameUpdated();
				}
			}
		}

		private string _aprsId;
		public string AprsId
		{
			get
			{
				return _aprsId ?? string.Empty;
			}
			set
			{
				if ( _aprsId != value )
				{
					_aprsId = value;
					OnPropertyChanged( "AprsId" );
					if ( !_isPostUpdate )
					{
						if ( !( IsMobile && HasAprsId && NetActivation.Instance.UpdateAprs( this ) ) )
							DoNotifications( NetActivityTypes.NoActivity );
					}
				}
			}
		}

		public bool HasAprsId
		{
			get
			{
				return !string.IsNullOrWhiteSpace( _aprsId );
			}
		}

		private ObservableCollection<string> _aprsMatches = new ObservableCollection<string>();
		public ObservableCollection<string> AprsMatches
		{
			get
			{
				return _aprsMatches;
			}
		}

		private NetLocation _location;
		public NetLocation Location
		{
			get
			{
				return _location;
			}
			private set
			{
				if ( _location != null )
					_location.PropertyChanged -= new PropertyChangedEventHandler( _location_PropertyChanged );
				_location = value;
				_location.PropertyChanged += new PropertyChangedEventHandler( _location_PropertyChanged );
				OnPropertyChanged( "Location" );
				OnPropertyChanged( "LocationText" );
				if ( _location.IsVerified )
					NetActivation.Instance.AdjustNearestStationSort( this );
			}
		}

		void _location_PropertyChanged( object sender, PropertyChangedEventArgs e )
		{
			if ( e.PropertyName == "Location" )
			{
				OnPropertyChanged( "LocationText" );
				NetActivation.Instance.AdjustNearestStationSort( this );
				if ( _location.IsVerified )
					DoNotifications( NetActivityTypes.LocationVerified );
				else
					DoNotifications( NetActivityTypes.LocationChanged );
			}
		}

		public string LocationText
		{
			get
			{
				return _location.Location;
			}
			set
			{
				Location = new NetLocation( value );
			}
		}

		public bool CanPost
		{
			get
			{
				return ( !_isPosted || _inPropertyChange ) && !_isPostUpdate && _callsign != null && Regex.IsMatch( _callsign, NetActivation.CallsignRegEx ) &&
					!string.IsNullOrWhiteSpace( _name ) && _location.IsDefined;
			}
		}

		private DateTimeOffset _timeIn = DateTimeOffset.UtcNow;
		public string TimeInString
		{
			get
			{
				return _timeIn.ToLocalTime().ToString( Properties.Resources.TimeFormat );
			}
		}

		public DateTimeOffset TimeIn
		{
			get
			{
				return _timeIn;
			}
		}

		private DateTimeOffset? _timeOut = null;
		public string TimeOutString
		{
			get
			{
				if ( _timeOut.HasValue )
					return _timeOut.Value.ToLocalTime().ToString( Properties.Resources.TimeFormat );
				else
					return string.Empty;
			}
		}

		private bool _isNetControl;
		public bool IsNetControl
		{
			get
			{
				return _isNetControl;
			}
			set
			{
				if ( value )
				{
					IsAlternateNetControl = false;

					var netControl = ( from nc in NetActivation.Instance.Members
									   where nc.IsNetControl
									   select nc ).ToArray();
					foreach ( NetMember member in netControl )
					{
						member.IsNetControl = false;
						member.IsAlternateNetControl = true;
					}
				}

				_isNetControl = value;
				OnPropertyChanged( "IsNetControl" );
				DoNotifications( NetActivityTypes.NetControlChanged );
			}
		}

		private bool _isAlternateNetControl;
		public bool IsAlternateNetControl
		{
			get
			{
				return _isAlternateNetControl;
			}
			set
			{
				if ( _isAlternateNetControl != value )
				{
					if ( value )
					{
						var netControl = ( from nc in NetActivation.Instance.Members
										   where nc.IsAlternateNetControl
										   select nc ).ToArray();
						foreach ( NetMember member in netControl )
						{
							member.IsAlternateNetControl = false;
						}
					}
					_isAlternateNetControl = value;
					OnPropertyChanged( "IsAlternateNetControl" );
					DoNotifications( NetActivityTypes.AlternateNetControlChanged );
				}
			}
		}

		private bool _isSKYWARNLiaison;
		public bool IsSKYWARNLiaison
		{
			get
			{
				return _isSKYWARNLiaison;
			}
			set
			{
				if ( _isSKYWARNLiaison != value )
				{
					if ( value )
					{
						var netControl = ( from nc in NetActivation.Instance.Members
										   where nc.IsSKYWARNLiaison
										   select nc ).ToArray();
						foreach ( NetMember member in netControl )
						{
							member.IsSKYWARNLiaison = false;
						}
					}
					_isSKYWARNLiaison = value;
					OnPropertyChanged( "IsSKYWARNLiaison" );
					DoNotifications( NetActivityTypes.SKYWARNLiaisonChanged );
				}
			}
		}

		private bool _hasTraffic;
		public bool HasTraffic
		{
			get
			{
				return _hasTraffic;
			}
			set
			{
				if ( _hasTraffic != value )
				{
					_hasTraffic = value;
					OnPropertyChanged( "HasTraffic" );
					DoNotifications( NetActivityTypes.NoActivity );
				}
			}
		}

		private bool _isActive = true;
		public bool IsActive
		{
			get
			{
				return _isActive;
			}
			set
			{
				if ( _isActive != value )
				{
					_isActive = value;
					if ( _isActive )
					{
						_timeIn = DateTimeOffset.UtcNow;
						_timeOut = null;
						OnPropertyChanged( "TimeIn" );
						OnPropertyChanged( "TimeInString" );
						OnPropertyChanged( "TimeOutString" );
						DoNotifications( NetActivityTypes.CheckIn );
					}
					else
					{
						_timeOut = DateTimeOffset.UtcNow;
						OnPropertyChanged( "TimeOutString" );
						IsNetControl = false;
						IsAlternateNetControl = false;
						IsSKYWARNLiaison = false;
						HasTraffic = false;
						DoNotifications( NetActivityTypes.CheckOut );
					}
                    if ( !_inConstruction )
						NetActivation.Instance.AdjustMemberGrouping( this );
				}
			}
		}

		private bool _isMobile;
		public bool IsMobile
		{
			get
			{
				return _isMobile;
			}
			set
			{
				NetFormerMember
					formerMember;

				if ( _isMobile != value )
				{
					_isMobile = value;
					if ( _isMobile )
					{
						_baseLocation = Location;
					}
					else if ( _baseLocation != null )
					{
						Location = _baseLocation;
						_baseLocation = null;
					}
					else if ( NetActivation.Instance.GetFormerMember( _callsign, out formerMember ) )
					{
						if ( formerMember.IsVerified )
							Location = new NetLocation( formerMember );
					}
					OnPropertyChanged( "IsMobile" );
					DoNotifications( NetActivityTypes.MobileBaseChanged );
				}
			}
		}

		private LinkedList<NetActivity> _reports = new LinkedList<NetActivity>();
		public IEnumerable<NetActivity> Reports
		{
			get
			{
				return _reports;
			}
		}

		private string _lastReport;
		public string LastReport
		{
			get
			{
				return _lastReport ?? string.Empty;
			}
		}

		private NetReport _newReport;
		public NetReport NewReport
		{
			get
			{
				return _newReport;
			}
		}

		private string _phoneNumber;
		public string PhoneNumber
		{
			get
			{
				return _phoneNumber ?? string.Empty;
			}
		}

		private bool _isPhoneOnly;
		public bool IsPhoneOnly
		{
			get
			{
				return _isPhoneOnly;
			}
		}

		private double _distanceToLocation = double.NaN;
		public double DistanceToLocation
		{
			get
			{
				return _distanceToLocation;
			}
		}

		public string NearestStationLabel
		{
			get
			{
				if ( double.IsNaN( _distanceToLocation ) )
					return ToString();
				else
					return string.Format( Properties.Resources.NearestStationFormat, this, _distanceToLocation );
			}
		}

		public string Group
		{
			get
			{
				return IsActive ? Properties.Resources.Active : Properties.Resources.Inactive;
			}
		}

		public int GroupSort
		{
			get
			{
				return IsActive ? 0 : 1;
			}
		}

		public NetMember()
		{
			Location = new NetLocation();
		}

		public NetMember( XElement node )
		{
			_inConstruction = true;
			Update( node );
			_inConstruction = false;
		}

		public void AddReport( NetActivity report )
		{
			if ( !_reports.Contains( report ) )
			{
				_reports.AddFirst( report );
				if ( report.IsLocal )
					NetActivation.Instance.AddActivity( report );

				NetActivation.Instance.MapProxy.UpdateMarker( report );
				NetActivation.Instance.MapProxy.UpdateMarker( this );

				_lastReport = ( from r in _reports
								orderby r.Time descending
								select r.Details ).First();

				StartNewReport();

				OnPropertyChanged( "LastReport" );
				OnPropertyChanged( "NewReport" );
			}
		}

		public void StartNewReport()
		{
			_newReport = new NetReport( this );
		}

		public void Initialize( NetFormerMember formerMember )
		{
			Name = formerMember.Name;
			AprsId = formerMember.AprsId;
			if ( formerMember.IsVerified )
			{
				Location = new NetLocation( formerMember );
				_location_PropertyChanged( this, new PropertyChangedEventArgs( "Location" ) );
			}
			else if ( formerMember.HasLocation )
				LocationText = formerMember.Location;
		}

		public void Update( XElement node )
		{
			bool
				aprsIdSet = false;

			_isPosted = true;
			_isPostUpdate = true;

			foreach ( XElement item in node.Elements() )
			{
				switch ( item.Name.LocalName )
				{
					case "roster_id":
						Callsign = NetPost.DecodedString( item );
						break;
					case "name":
						Name = NetPost.DecodedString( item );
						break;
					case "aprs_id":
						AprsId = NetPost.DecodedString( item );
						aprsIdSet = true;
						break;
					case "is_net_control":
						IsNetControl = NetPost.ToBoolean( item );
						break;
					case "is_alt_control":
						IsAlternateNetControl = NetPost.ToBoolean( item );
						break;
					case "is_skywarn":
						IsSKYWARNLiaison = NetPost.ToBoolean( item );
						break;
					case "has_traffic":
						HasTraffic = NetPost.ToBoolean( item );
						break;
					case "is_mobile":
						IsMobile = NetPost.ToBoolean( item );
						break;
					case "is_active":
						IsActive = NetPost.ToBoolean( item );
						break;
					case "time_in":
						_timeIn = NetPost.FromMySQLDate( item );
						OnPropertyChanged( "TimeIn" );
						OnPropertyChanged( "TimeInString" );
						break;
					case "time_out":
						_timeOut = NetPost.FromMySQLDate( item );
						OnPropertyChanged( "TimeOutString" );
						break;
					case "is_phone_only":
						_isPhoneOnly = NetPost.ToBoolean( item );
						OnPropertyChanged( "IsPhoneOnly" );
						break;
					case "phone_number":
						_phoneNumber = NetPost.DecodedString( item );
						OnPropertyChanged( "PhoneNumber" );
						break;
					case "update_id":
						_updateId = Guid.Parse( NetPost.DecodedString( item ) );
						break;
				}
			}

			if ( !aprsIdSet )
				AprsId = null;

			Location = new NetLocation( node );

			NetActivation.Instance.MapProxy.UpdateMarker( this );
			foreach ( NetActivity item in _reports )
			{
				NetActivation.Instance.MapProxy.UpdateMarker( item );
			}

			_isPostUpdate = false;
		}

		public void UpdateAprs( NetAprs aprsData )
		{
			NetLocation
				aprsLocation;
			bool
				locationChanged;

			if ( IsMobile )
			{
				aprsLocation = new NetLocation( aprsData );
				locationChanged = !Location.Equals( aprsData );
				if ( locationChanged || !Location.IsAprsLocation )
				{
					Location = aprsLocation;
					DoNotifications( NetActivityTypes.NoActivity );
				}
				else if ( Location.UpdateAprsTime( aprsData ) )
					DoNotifications( NetActivityTypes.NoActivity );

				if ( locationChanged )
				{
					NetActivation.Instance.MapProxy.UpdateMarker( this );
					foreach ( NetActivity item in _reports )
						NetActivation.Instance.MapProxy.UpdateMarker( item );
				}
			}
		}

		public void UpdateAprsMatches()
		{
			string
				aprsId = _aprsId;
			bool
				isPostUpdate = _isPostUpdate;

			_isPostUpdate = true; // prevent any online updates to APRS ID
			_aprsMatches.Clear();
			foreach ( string item in NetActivation.Instance.GetAprsMatches( _callsign ) )
				_aprsMatches.Add( item );
			AprsId = aprsId;
			_isPostUpdate = isPostUpdate;
		}

		public void CalculateDistanceToLocation( NetLocation location )
		{
			_distanceToLocation = _location.CalculateDistance( location );
			OnPropertyChanged( "DistanceToLocation" );
			OnPropertyChanged( "NearestStationLabel" );
		}

		private string _toString;
		public override string ToString()
		{
			return _toString ?? ( _toString = string.Format( Properties.Resources.MemberFormat, _callsign, _name ) );
		}

		public override bool Equals( object obj )
		{
			if ( obj is NetMember )
				return ( ( NetMember ) obj )._callsign == _callsign;
			else
				return false;
		}

		public override int GetHashCode()
		{
			return _callsign.GetHashCode();
		}

		private void DoNotifications( NetActivityTypes activityType )
		{
			_inPropertyChange = true;

			if ( CanPost )
			{
				if ( _isPosted )
				{
					NetActivation.Instance.QueueNetPost( this );

					switch ( activityType )
					{
						case NetActivityTypes.CheckIn:
							NetActivation.Instance.AddActivity( new NetActivity( _timeIn, activityType, string.Format(
								Properties.Resources.CheckInDetails, this, _timeIn ), this ) );
							NetActivation.Instance.MapProxy.UpdateMarker( this );
							break;
						case NetActivityTypes.CheckOut:
							NetActivation.Instance.AddActivity( new NetActivity( _timeOut.Value, activityType, string.Format(
								Properties.Resources.CheckOutDetails, this, _timeOut.Value ), this ) );
							NetActivation.Instance.MapProxy.UpdateMarker( this );
							foreach ( NetActivity item in _reports )
							{
								NetActivation.Instance.MapProxy.UpdateMarker( item );
							}
							break;
						case NetActivityTypes.NetControlChanged:
							if ( _isNetControl )
							{
								NetActivation.Instance.AddActivity( new NetActivity( DateTimeOffset.UtcNow, activityType, string.Format(
									Properties.Resources.NetControlChangedDetails, this ), this ) );
							}
							break;
						case NetActivityTypes.AlternateNetControlChanged:
							if ( _isAlternateNetControl )
							{
								NetActivation.Instance.AddActivity( new NetActivity( DateTimeOffset.UtcNow, activityType, string.Format(
									Properties.Resources.AlternateNetControlChangedDetails, this ), this ) );
							}
							break;
						case NetActivityTypes.SKYWARNLiaisonChanged:
							if ( _isSKYWARNLiaison )
							{
								NetActivation.Instance.AddActivity( new NetActivity( DateTimeOffset.UtcNow, activityType, string.Format(
									Properties.Resources.SKYWARNLiaisonChangedDetails, this ), this ) );
							}
							break;
						case NetActivityTypes.MobileBaseChanged:
							NetActivation.Instance.AddActivity( new NetActivity( DateTimeOffset.UtcNow, activityType, string.Format(
								Properties.Resources.MobileBaseChangedDetails, this, _isMobile ? Properties.Resources.MobileStatus :
								Properties.Resources.BaseStatus ), this ) );
							NetActivation.Instance.MapProxy.UpdateMarker( this );
							break;
						case NetActivityTypes.LocationChanged:
							NetActivation.Instance.AddActivity( new NetActivity( DateTimeOffset.UtcNow, activityType, string.Format(
								Properties.Resources.LocationChangedDetails, this, _location ), this ) );
							NetActivation.Instance.MapProxy.UpdateMarker( this );
							foreach ( NetActivity item in _reports )
							{
								NetActivation.Instance.MapProxy.UpdateMarker( item );
							}
							break;
						case NetActivityTypes.LocationVerified:
							NetActivation.Instance.AddActivity( new NetActivity( DateTimeOffset.UtcNow, activityType, string.Format(
								Properties.Resources.LocationVerifiedDetails, this, _location ), this ) );
							NetActivation.Instance.MapProxy.UpdateMarker( this );
							foreach ( NetActivity item in _reports )
							{
								NetActivation.Instance.MapProxy.UpdateMarker( item );
							}
							break;
					}
				}
				else
				{
					NetActivation.Instance.AddMember( this );
					NetActivation.Instance.AddActivity( new NetActivity( _timeIn, NetActivityTypes.CheckIn, string.Format(
						Properties.Resources.CheckInDetails, this, _timeIn ), this ) );
					_isPosted = true;
				}
			}

			_inPropertyChange = false;
		}

		public NetPost GetNetPost()
		{
			NetPost
				post;

			if ( CanPost )
			{
				post = new NetPost( NetActivation.RosterPage );
				post.Data[ "net_id" ] = NetActivation.Instance.ActivationId.ToString();
				post.Data[ "roster_id" ] = _callsign;
				if ( !string.IsNullOrWhiteSpace( _previousCallsign ) )
				{
					post.Data[ "prev_roster_id" ] = _previousCallsign;
					_previousCallsign = null;
				}
				if ( !string.IsNullOrWhiteSpace( _aprsId ) )
					post.Data[ "aprs_id" ] = _aprsId;
				post.Data[ "name" ] = _name;
				post.Data[ "location" ] = _location.Location;
				if ( !_location.IsGps )
				{
					post.Data[ "latitude" ] = _location.Latitude.ToString();
					post.Data[ "longitude" ] = _location.Longitude.ToString();
				}
				if ( _location.IsAprsLocation )
					post.Data[ "aprs_time" ] = NetPost.ToMySQLDate( _location.AprsTime.Value );
				post.Data[ "has_rev_geo" ] = NetPost.FromBoolean( _location.HasReverseGeocode );
				post.Data[ "is_net_control" ] = NetPost.FromBoolean( _isNetControl );
				post.Data[ "is_alt_control" ] = NetPost.FromBoolean( _isAlternateNetControl );
				post.Data[ "is_skywarn" ] = NetPost.FromBoolean( _isSKYWARNLiaison );
				post.Data[ "has_traffic" ] = NetPost.FromBoolean( _hasTraffic );
				post.Data[ "is_mobile" ] = NetPost.FromBoolean( _isMobile );
				post.Data[ "is_active" ] = NetPost.FromBoolean( _isActive );
				post.Data[ "time_in" ] = NetPost.ToMySQLDate( _timeIn );
				if ( _timeOut.HasValue )
					post.Data[ "time_out" ] = NetPost.ToMySQLDate( _timeOut.Value );
				post.Data[ "logger_id" ] = NetActivation.Instance.LoggerId;
				post.Data[ "is_posted" ] = NetPost.FromBoolean( IsPosted );
				post.Data[ "prev_update_id" ] = _updateId.ToString();
				_updateId = Guid.NewGuid();
				post.Data[ "update_id" ] = _updateId.ToString();
			}
			else
				post = null;

			return post;
		}

		public bool IsVisible
		{
			get
			{
				return _location.IsVerified;
			}
		}

		public string Label
		{
			get
			{
				return _callsign;
			}
		}

		public double Latitude
		{
			get
			{
				return _location.Latitude;
			}
		}

		public double Longitude
		{
			get
			{
				return _location.Longitude;
			}
		}

		public string IconFile
		{
			get
			{
				bool
					hasReports = _reports.Any( r => r.Location.Equals( _location ) );

				if ( _isMobile )
					return hasReports ? MemberMobileReportIconFile : MemberMobileIconFile;
				else
					return hasReports ? MemberBaseReportIconFile : MemberBaseIconFile;
			}
		}

		public bool Equals( INetMapMarker other )
		{
			return Label == other.Label;
		}
	}
}
